認識 Dart 程式語言,從官方提供的dart-cheatsheet
掌握該語言的特色
這邊的行為與 JS (ES6) 語法類似,我們直接看範例程式。
class Age {
int age;
Age(this.age);
bool get isComeOfAge {
return age >= 18;
}
}
class User {
String name;
Age? _age;
User(this.name, [int? age]) {
if (age != null) {
this._age = Age(age);
}
}
int? get age {
return _age?.age;
}
set age(int? value) {
if (value != null) {
_age = Age(value);
}
}
bool get isComeOfAge {
return _age!.isComeOfAge;
}
}
void main() {
// age 為可選參數
var user = User("Leo");
print(user.age); // null
user.age = 35;
print(user.age); // 35
print(user.isComeOfAge); // true
}
類似 JS (ES11) 上的 Optional chaining,用來保護 Object 上的屬性存取。
這邊以 Getters and Settiers 中的範例為例,因為年齡可為空,因為在取用前需要特別加上(?.)。
int? get age {
return _age?.age;
}
Dart參數傳遞的方式有兩種:位置參數以及命名參數。
這邊以 Getters and Settiers 中的範例為例:
因為年齡可為空,可選位置參數放在方法參數的最後並使用中刮號[]
包起來。
class User {
String name;
Age? _age;
User(this.name, [int? age]) {
if (age != null) {
this._age = Age(age);
}
}
...
}
我們改寫一下上面User
類別的建構式方法,因為年齡可為空,可選命名參數使用大刮號{}
包起來。
在 Flutter 控件的原始碼,語法大多是以可選命名參數進行設計,當可選參數較多時這種命名參數的可讀性很高。
class User {
String name;
Age? _age;
User(this.name, {Age? age}) {
if (age != null) {
this._age = age;
}
}
...
}
void main() {
var user = User("Leo", age: 35);
print(user.age); // 35
}
注意:一個方法不能同時使用可選位置參數和可選命名參數。
對於例於處理,我們可以使用 try..catch 的方式補獲可能的例外狀況,而不影響到主程式的運行,與 JS 不同的地方在於 dart 可指定多個 catch 語句。請參考下列範例:
class CustomException implements Exception {
String cause;
CustomException(this.cause);
}
class LogicalException implements Exception {
String _cause = "打我啊笨蛋";
toString() {
return "LogicalException: $_cause";
}
}
somethingWillThrowError(type) {
switch (type) {
case 0:
throw CustomException("Sing Hosanna");
case 1:
throw LogicalException();
default:
throw "Unknown Error";
}
}
void test() {
try {
somethingWillThrowError(DateTime.now().microsecondsSinceEpoch % 3);
} on CustomException {
print("? He's got the whole world in His hands~");
} on Exception catch (e) {
// 取得Exception類型的錯誤
print('Exception: ${e}');
rethrow;
} catch (e) {
// 取得未知類型的錯誤
print("$e");
}
}
void main() {
try {
test();
} on LogicalException catch (e) {
print(e.runtimeType); // LogicalException
print(e.toString());
} finally {
print("I Got You!!!");
}
}
在建構方法上有提供不同類型的建構方式,查看下方的範例:
enum JOB { NEW, FIGHTER, WARRIOR }
class Fighter extends Player {
final JOB _job = JOB.FIGHTER;
Fighter(name) : super(name: name);
}
class Warrior extends Player {
final JOB _job = JOB.WARRIOR;
Warrior(name) : super(name: name);
}
class Player {
String name;
JOB _job;
// 在建構式中使用 this,可以用來對應類別的屬性
Player({required this.name}) : _job = JOB.NEW;
// 在建構式函式執行可使用 (:) 進行初始化的動作
Player.fighter({required this.name}) : _job = JOB.FIGHTER {
print("Build: Player.fighter [$job]");
}
// 可以定義不同建構方法
Player.fromFighter(String name) : this.fighter(name: name);
// 工廠方式,使用 factory 宣告讓方式會返回 子類別 或是 null
factory Player.fromJson(Map<String, String> json) {
var name = json['name']!;
var job = json['job']!;
if (job == 'fighter') return Fighter(name);
if (job == 'warrior') return Warrior(name);
throw ArgumentError('Unrecognized $job');
}
get job {
return this._job;
}
toSring() {
return "$name $job";
}
}
void main() {
var newPlayer = Player(name: "新手");
print(newPlayer.toSring());
// 此類別重新轉達其他的建構方法 (這邊範例實作命名參數轉成位置參數的建構方法)
var fighter = Player.fromFighter("戰士");
print(fighter.toSring());
var warrior = Player.fromJson({"name": "鬥士", "job": "warrior"});
print(warrior.toSring());
}
在之前的範例中,我們大部份都是使用var
讓 dart 透過推論的方式導出變數的型別。
如果我們想要宣告一個變數為常數,我們可以使用關鍵字final
或是const
取代var
修飾其存取行為,這兩個的差別如下:
final
的變數只可以被賦值一次,const
的變數指的是編譯時的常數,這邊指是在編譯後,其值將永遠不會被改變。
在類別中的實例(instance)變數可以是final
但不可以是const
雖然 final
的 object 無法被修改,但是其屬性是可以被改變的;相對的 const
的 object 其屬性是 immutable 的喔。
class Area {
String city = "";
Area(this.city);
}
class User {
final String name; // 2. 不可為 const
final Area area;
User(this.name, this.area);
// 3. 使用 const 需加上 static 表示有靜態常數
static const value = 999;
}
void main() {
final aFinalStr = "hi";
// aFinalStr = "cc"; // 1. 不可修改
print(aFinalStr);
const bConstStr = "hello";
// bConstStr = "cc"; // 1. 不可修改
print(bConstStr);
var user = User("Leo", Area("Taiwan"));
// user.name = "cc"; // 1. 不可修改
print(user.name);
print(user.area.city);
user.area.city = "Taipei";
// user.area = Area("Tainan"); // 3. 不可修改
print(user.area.city); // 3. 被異動了
print(User.value);
}
這邊我直接引用官網上的範例,概念與上述類別的靜態常數是一樣的,當你定義的對象永遠不會改變時,可以宣告一個 const
建構式用來在編譯中建立靜態常數物件。
class ImmutablePoint {
static const ImmutablePoint origin = ImmutablePoint(0, 0);
final int x;
final int y;
const ImmutablePoint(this.x, this.y);
}
Mixin是一種在多重繼承中複用方法的模式,使用關鍵字 with
將 Mixin 類別中方法混入至想要複用其方法的類別上。
P.S. Mixin沒有收錄在速查表上,不過其語法的特殊性,我覺得還是需要特別提一下。因為在之後的 Flutter SDK 內的 runApp
函數裡有一個類別 WidgetsFlutterBinding
就應用到多個Binding的mixin模式,而建構出 Flutter 運行的底層架構。這邊我們先從下列簡單的範例認識一下 mixin 的使用方法。
mixin Hello {
late String name;
void say() {
print("Hi, $name !!!");
}
}
class User with Hello {
String name;
User(this.name);
}
void main() {
var user = User("Leo");
user.say(); // Hi, Leo !!!
}
麻煩至官方速查表中,完成今日學習的章節裡的代碼範列,連結在這。
今日完成速查表中列出的dart語言特性的學習,當然還有許多課程內容包含泛型、同步非同步、核心函式庫、第三方套件等需要時間慢慢上手。